package com.mongo.entity;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.mongo.service.Model;
import com.mongo.util.ReflectionUtil;
import com.mongo.util.SecretFactory;
import com.mongo.util.SpringUtils;
import com.mongodb.client.result.DeleteResult;
import com.mongodb.client.result.UpdateResult;
import lombok.SneakyThrows;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 继承此类可调用增删改功能
 * @author xiazhun
 * @create 2022/8/5
 */
@SuppressWarnings("all")
public abstract class BaseEntity<T extends BaseEntity<?>> implements Model<T>, Serializable {

    private static final MongoTemplate mongoTemplate = SpringUtils.getBean(MongoTemplate.class);

    private transient Class<T> clazz;

    public static final String ID = "id";

    @Id
    private String id;

    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date createTime = new Date();

    public Class<T> getClazz(){
        ParameterizedType pt = (ParameterizedType) this.getClass().getGenericSuperclass();
        // 获取第一个类型参数的真实类型
        this.clazz = (Class<T>) pt.getActualTypeArguments()[0];
        return clazz;
    }

    @SneakyThrows
    @Override
    public T insert() {
        BaseEntity<T> encrypt = SecretFactory.encrypt(true, this);
        Document document = getDocument(getClazz());
        Map<String, Object> map = beanToMap(encrypt);
        if (!ObjectUtils.isEmpty(document)){
            Map<String, Object> save = mongoTemplate.save(map, document.value());
            save.put("id",save.get("_id"));
            return (T) mapToBean(save,getClazz());
        }
        return null;
    }

    /**
     * map转对象
     * @param map
     * @param beanClass
     * @param <T>
     * @return
     * @throws Exception
     */
    public static <T> T mapToBean(Map map, Class<T> beanClass) throws Exception {
        T object = beanClass.newInstance();
        Field[] fields = object.getClass().getDeclaredFields();
        for (Field field : fields) {
            int mod = field.getModifiers();
            if (Modifier.isStatic(mod) || Modifier.isFinal(mod)) {
                continue;
            }
            field.setAccessible(true);
            if (map.containsKey(field.getName())) {
                field.set(object, map.get(field.getName()));
            }
        }
        return object;
    }

    /**
     * 对象转Map
     * @param object
     * @return
     * @throws IllegalAccessException
     */
    public static Map beanToMap(Object object){
        Map<String, Object> map = new HashMap<String, Object>();
        try {
            Field[] fields = object.getClass().getDeclaredFields();
            for (Field field : fields) {
                field.setAccessible(true);
                map.put(field.getName(), field.get(object));
            }
        }catch (Exception e){
            throw new IllegalArgumentException(e.getMessage());
        }
        return map;
    }

    @Override
    public long delete() {
        Query query = new Query();
        query.addCriteria(Criteria.where("_id").is(id));
        DeleteResult remove = mongoTemplate.remove(query, getClazz());
        return remove.getDeletedCount();
    }

    @Override
    @SuppressWarnings("all")
    public long update() {
        Update update = new Update();
        BaseEntity<T> encrypt = SecretFactory.encrypt(true, this);
        Query query = new Query(Criteria.where("_id").is(id));
        Field[] declaredFields = getClass().getDeclaredFields();
        for (Field declaredField : declaredFields) {
            Object fieldValue = ReflectionUtil.getValue(encrypt, declaredField);
            update.set(declaredField.getName(),fieldValue);
        }
        UpdateResult updateResult = mongoTemplate.updateFirst(query, update, getClazz());
        return updateResult.getMatchedCount();
    }

    @Override
    public List<T> selectAll() {
        List<T> all = mongoTemplate.findAll(getClazz());
        return all.stream().map(entity -> SecretFactory.decode(true,entity)).collect(Collectors.toList());
    }

    @Override
    public T selectById() {
        T t = mongoTemplate.findById(id, getClazz());
        return SecretFactory.decode(true,t);
    }

    public static Document getDocument(Class<?> clazz){
        Document annotation = AnnotationUtils.findAnnotation(clazz, Document.class);
        if (Objects.nonNull(annotation)){
            return annotation;
        }
        return null;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }
}
